#define WINDOWWIDTH 800
#define WINDOWHEIGHT 600

#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

void FramebufferResize(GLFWwindow* window, int width, int height);

//顶点着色器 vertex shader
const char* VertexShaderSource = 
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n" //in 表示输入数据, 从 location 位置输入
"\n"
"void main()\n"
"{\n"
"    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"    //顶点着色器的输出, gl_Position为预定义值
"}\n";

//片段着色器 fragment shader
const char* FragmentShaderSource = 
"#version 330 core\n"
"out vec4 FragColor;\n" //声明输出
"\n"
"void main()\n"
"{\n"
"    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"   //输出颜色
"}\n";

void CheckCompileShader(GLuint Shader, const char* Marker);
void CheckLinkShaderProgram(GLuint ShaderProgram, const char* Marker);

int main()
{
    //初始化 glfw
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //初始化窗口
    GLFWwindow* Window = glfwCreateWindow(WINDOWWIDTH, WINDOWHEIGHT, "learnopengl-hellotriangle", NULL, NULL);
    if (Window == NULL)
    {
        glfwTerminate();
        std::cout << "Window Initialize Fail !" << std::endl;
        return -1;
    }
    glfwMakeContextCurrent(Window);
    glfwSetFramebufferSizeCallback(Window, FramebufferResize);

    //初始化 glad
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        glfwTerminate();
        std::cout << "GLAD Initialize Fail !" << std::endl;
        return -1;
    }

    //顶点着色
    unsigned int VertexShader = glCreateShader(GL_VERTEX_SHADER);   //创建着色器
    glShaderSource(VertexShader, 1, &VertexShaderSource, NULL); //绑定shader代码
    glCompileShader(VertexShader);   //编译shader
    CheckCompileShader(VertexShader, "Vertex Shader");

    //片段着色
    unsigned int FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);   //创建着色器
    glShaderSource(FragmentShader, 1, &FragmentShaderSource, NULL); //绑定shader代码
    glCompileShader(FragmentShader);   //编译shader
    CheckCompileShader(FragmentShader, "Fragment Shader");

    //绑定 VAO vertex array object
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    float Vertices[] = {
        0.5f, 0.5f, 0.0f,   // 右上角
        0.5f, -0.5f, 0.0f,  // 右下角
        -0.5f, -0.5f, 0.0f, // 左下角
        -0.5f, 0.5f, 0.0f   // 左上角
    };

    //顶点数据 (z 为深度)
    // 1. 复制顶点数组到缓冲中供OpenGL使用
    unsigned int VBO;   //vertex buffer object
    glGenBuffers(1, &VBO);  //生成缓冲对象, 用于写入显存
    glBindBuffer(GL_ARRAY_BUFFER, VBO); //将 VBO 绑定至缓冲类型为 GL_ARRAY_BUFFER 的状态上
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);  //写入数据

    //链接顶点属性
    // 2. 设置顶点属性指针
    /*
    * index: position顶点属性的位置值(顶点着色器中使用layout(location = 0))
    * size:  顶点属性的大小
    * type:  指定数据的类型
    * normalized: 是否希望数据被标准化
    * stride:步长, 顶点属性组之间的间隔
    * void* pointer: 起始位置的偏移量
    */  //该顶点属性会链接至 绑定到GL_ARRAY_BUFFER 的 VBO, 即顶点数据
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    glEnableVertexAttribArray(0);   //将 index 为 0 的顶点属性进行应用

    unsigned int Indices[] = {
        // 注意索引从0开始! 
        // 此例的索引(0,1,2,3)就是顶点数组vertices的下标，
        // 这样可以由下标代表顶点组合成矩形

        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };
    unsigned int EBO;   //element buffer object
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);


    glBindBuffer(GL_ARRAY_BUFFER, 0);   //AttribPointer 注册结束后, 可解除 OpenGL 对 buffer 的绑定

    glBindVertexArray(0);   //vbo、顶点属性设置完成后, 可解绑当前vao, 避免影响后续 vao

    //着色器程序
    // 3. 当我们渲染一个物体时要使用着色器程序
    unsigned int ShaderProgram;
    ShaderProgram = glCreateProgram();
    glAttachShader(ShaderProgram, VertexShader);    //将 shader 附加至 program 中
    glAttachShader(ShaderProgram, FragmentShader);
    glLinkProgram(ShaderProgram);   //链接
    CheckLinkShaderProgram(ShaderProgram, "Shader Program");

    //删除 shader 对象, 在链接 program 之后
    glDeleteShader(VertexShader);
    glDeleteShader(FragmentShader);

    //解除注释得到线框模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);    //默认填充

    //render
    while (!glfwWindowShouldClose(Window))
    {
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUseProgram(ShaderProgram);
        glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glBindVertexArray(0);

        glfwSwapBuffers(Window);
        glfwPollEvents();
    }

    return 0;
}

void FramebufferResize(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void CheckCompileShader(GLuint Shader, const char* Marker)
{
    int Success;
    char InfoLog[512];
    glGetShaderiv(Shader, GL_COMPILE_STATUS, &Success);

    if (!Success)
    {
        glGetShaderInfoLog(Shader, 512, NULL, InfoLog);
        std::cout << "ERROR::SHADER::" << Marker << "::COMPILATION_FAILED\n"
            << InfoLog << std::endl;
    }
}

void CheckLinkShaderProgram(GLuint ShaderProgram, const char* Marker)
{
    int Success;
    char InfoLog[512];
    glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);

    if (!Success)
    {
        glGetProgramInfoLog(ShaderProgram, 512, NULL, InfoLog);
        std::cout << "ERROR::SHADER_PROGRAM::" << Marker << "::LINK_FAILED\n"
            << InfoLog << std::endl;
    }
}